#ifndef FEM_COMMON_LOG
#define FEM_COMMON_LOG

#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <mpi.h>
#include <sys/time.h>

inline double get_time_fem(struct timeval *start, struct timeval *end)
{
    return end->tv_sec - start->tv_sec + (end->tv_usec - start->tv_usec) * 1e-6;
}

enum class LogLevel {
    DEBUG,
    INFO,
    ERROR
};

class Ansi {
public:
    static std::string Code(int code) {
        return "\033[" + std::to_string(code) + "m";
    }
};

class Logger {
public:
    static Logger& getInstance() {
        static Logger instance;
        return instance;
    }

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    void setLogLevel(LogLevel level) {
        logLevel = level;
    }

    void log(const std::string& message, LogLevel level) {
        if (static_cast<int>(level) < static_cast<int>(logLevel)) {
            return;
        }

        std::lock_guard<std::mutex> lock(logMutex);

        std::string levelStr;
        std::string colorCode;
        switch (level) {
            case LogLevel::INFO:
                levelStr = "INFO";
                colorCode = Ansi::Code(94);
                break;
            case LogLevel::DEBUG:
                levelStr = "DEBUG";
                colorCode = Ansi::Code(93);
                break;
            case LogLevel::ERROR:
                levelStr = "ERROR";
                colorCode = Ansi::Code(91);
                break;
        }

        std::string logMessage = colorCode + "[" + levelStr + "] " + message + Ansi::Code(0) + "\n";

        std::cout << logMessage << std::endl;

        if (logFile.is_open()) {
            logFile << "[" + levelStr + "] " + message << std::endl;
        }
    }

private:
    Logger() : logLevel(LogLevel::INFO) {
        int pid;
        MPI_Comm_rank(MPI_COMM_WORLD, &pid);
        std::string logfile = "logfile" + to_string(pid) + ".log";
        logFile.open(logfile, std::ios::out | std::ios::app);
    }

    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }

    LogLevel logLevel;
    std::ofstream logFile; 
    std::mutex logMutex; 
};

#define LOG_INFO(message)  Logger::getInstance().log(message, LogLevel::INFO)
#define LOG_DEBUG(message) Logger::getInstance().log(message, LogLevel::DEBUG)
#define LOG_ERROR(message) Logger::getInstance().log(message, LogLevel::ERROR)

#endif /*** FEM_COMMON_LOG ***/
